# -*- coding: utf-8 -*-

# -----------------------------------------------------------------------------
# 库文件
# -----------------------------------------------------------------------------
import time
import datetime
import os
import sys
import serial
import serial.tools.list_ports

import json_config

from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QComboBox, QPushButton, QTextEdit, QLabel, QLineEdit, QMessageBox, QFileDialog, QGraphicsView, QGraphicsScene, QHBoxLayout, QCheckBox, QGraphicsPathItem, QGraphicsItem
from PySide6.QtCore import Qt, QTimer, QThread, Signal, QObject, QMetaObject, Q_ARG, QCoreApplication, QPointF
from PySide6.QtGui import QIcon, QTextCursor, QPolygonF, QPainterPath, QPainter, QPen, QBrush, QColor

from functools import partial

import numpy as np
import pyqtgraph as pg

# -----------------------------------------------------------------------------
# RealTimePlot
# -----------------------------------------------------------------------------


class RealTimePlotApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.load_parameters()
        self.initialize_UI()
        self.update_serial_ports()

        self.is_connected = False
        self.is_plotting = False

        self.timer = QTimer(self)  # 创建定时器
        self.timer.timeout.connect(self.update_plot)  # 连接定时器到绘图更新函数
        self.timer_interval = 100  # 定时器间隔（单位：毫秒）

    def __del__(self):
        if hasattr(self, 'plot_thread') and self.plot_thread.isRunning():
            self.plot_updater.stop()
            self.plot_thread.quit()
            self.plot_thread.wait()

        if hasattr(self, 'read_thread') and self.read_thread.isRunning():
            self.serial_reader.stop()
            self.read_thread.quit()
            self.read_thread.wait()


    def load_parameters(self):
        self.config = json_config.JsonConfig("config.json")

        self.title = self.config.data["window"]["title"]
        self.width = self.config.data["window"]["width"]
        self.height = self.config.data["window"]["height"]

        self.log_path = self.config.data["log"]["log_path"]

        self.baudrate = self.config.data["serial_port"]["baudrate"]
        self.bytesize = self.config.data["serial_port"]["bytesize"]
        self.parity = self.config.data["serial_port"]["parity"]
        self.stopbits = self.config.data["serial_port"]["stopbits"]
        self.timeout = self.config.data["serial_port"]["timeout"]

        self.column = self.config.data["csv"]["column"]
        self.row = self.config.data["csv"]["row"]
        self.delimiter = self.config.data["csv"]["delimiter"]

        self.x_axis_length = self.config.data["plotting"]["x_axis_length"]
        self.update_freq = self.config.data["plotting"]["update_freq"]
        self.multi_plot_en = self.config.data["plotting"]["multi_plot_en"]
        self.multi_curve_en = self.config.data["plotting"]["multi_curve_en"]
        self.sum_en = self.config.data["plotting"]["sum_en"]

        self.index = 0
        self.update_period = 0

        self.serialport = None
        self.log_buffer = None

        if self.log_path == "":
            self.log_path = os.getcwd() + "/log/"
            self.config.data["log"]["log_path"] = self.log_path
            if not os.path.exists(self.log_path):
                os.makedirs(self.log_path)
                print("log path is not exist, create it")
            else:
                print("log path is exist")
        else:
            if not os.path.exists(self.log_path):
                os.makedirs(self.log_path)
                print("log path is not exist, create it")
            else:
                print("log path is exist")

        if self.sum_en:
            self.index = 0
        else:
            self.index = 100

        if self.update_freq == 0:
            self.update_freq = 100

        self.update_period = 1000/self.update_freq
        self.config.data["plotting"]["update_freq"] = self.update_freq

        self.config.save_data()

    def initialize_UI(self):

        self.setWindowTitle(self.title)
        self.resize(self.width, self.height)

        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        VL_1 = QVBoxLayout(self.central_widget)

        # Serial Port
        HL_1 = QHBoxLayout()
        self.serial_port_label = QLabel("Serial Port:")
        self.serial_port_combo = QComboBox()
        self.connect_button = QPushButton("CONNECT")
        self.connect_button.clicked.connect(self.handle_connect_button)\

        HL_1.addWidget(self.serial_port_label, 1)
        HL_1.addWidget(self.serial_port_combo, 10)
        HL_1.addWidget(self.connect_button, 2)

        VL_1.addLayout(HL_1)

        HL_2 = QHBoxLayout()
        VL_2 = QVBoxLayout()
        VL_3 = QVBoxLayout()

        HL_2.addLayout(VL_2, 4)
        HL_2.addLayout(VL_3, 1)

        VL_1.addLayout(HL_2)

        # Plotting Parameters
        self.column_label = QLabel("Column:")
        self.column_edit = QLineEdit()
        self.column_edit.setText(str(self.column))

        self.row_label = QLabel("Row:")
        self.row_edit = QLineEdit()
        self.row_edit.setText(str(self.row))

        self.delimiter_label = QLabel("Delimiter:")
        self.delimiter_edit = QLineEdit()
        self.delimiter_edit.setText(f"{self.delimiter}")

        self.summation_box = QCheckBox("Summation")
        self.summation_box.setChecked(self.sum_en)

        self.plot_button = QPushButton("PLOT")
        self.plot_button.clicked.connect(self.handle_plot_button)

        HL_3 = QHBoxLayout()
        HL_3.addWidget(self.column_label )
        HL_3.addWidget(self.column_edit)
        HL_3.addWidget(self.row_label)
        HL_3.addWidget(self.row_edit)
        HL_3.addWidget(self.delimiter_label)
        HL_3.addWidget(self.delimiter_edit)
        HL_3.addWidget(self.summation_box)
        HL_3.addWidget(self.plot_button)

        
        VL_2.addLayout(HL_3)

        # Plotting Area
        self.plot_label = QLabel("Plotting Area:")
        self.plot_curve = QGraphicsView()
        self.plot_scene = QGraphicsScene()

        self.plot_curve.setScene(self.plot_scene)
        VL_2.addWidget(self.plot_label)
        VL_2.addWidget(self.plot_curve)

        # Log Output
        self.log_output_label = QLabel("Log Output:")
        self.log_output = QTextEdit()
        self.log_output.setMaximumHeight(self.height * 0.25)
        self.log_output.setVisible(True)
        # self.log_output.setReadOnly(True)

        VL_2.addWidget(self.log_output_label)
        VL_2.addWidget(self.log_output)

        # HL_2 右侧, VL_3
        # -----------------------------------------------------------

        # Communication Type
        self.communication_type_label = QLabel("Communication Type:")
        self.communication_type_combo = QComboBox()
        self.communication_type_combo.addItem("SPI")
        self.communication_type_combo.addItem("I2C")

        HL_7 = QHBoxLayout()
        HL_7.addWidget(self.communication_type_label)
        HL_7.addWidget(self.communication_type_combo)

        VL_3.addLayout(HL_7)

        # Register Single Write/Read

        self.single_register_label = QLabel("Single Write/Read:")

        self.register_label = QLabel("Register:")
        self.register_edit = QLineEdit()
        self.register_edit.setText("0x00")

        self.value_label = QLabel("Value:")
        self.value_edit = QLineEdit()
        self.value_edit.setText("0x00")

        self.write_button = QPushButton("WRITE")
        self.write_button.clicked.connect(self.handle_write_button)

        self.read_button = QPushButton("READ")
        self.read_button.clicked.connect(self.handle_read_button)

        VL_3.addWidget(self.single_register_label)

        HL_4 = QHBoxLayout()
        HL_4.addWidget(self.register_label)
        HL_4.addWidget(self.register_edit)

        HL_5 = QHBoxLayout()
        HL_5.addWidget(self.value_label)
        HL_5.addWidget(self.value_edit)

        HL_6 = QHBoxLayout()
        HL_6.addWidget(self.write_button)
        HL_6.addWidget(self.read_button)

        VL_3.addLayout(HL_4)
        VL_3.addLayout(HL_5)
        VL_3.addLayout(HL_6)

        # Batch Write/Read

        self.batch_register_label = QLabel("Batch Write/Read:")

        VL_3.addWidget(self.batch_register_label)

        self.batch_write_label = QLabel("Batch Write:")
        self.batch_write_edit = QTextEdit()

        self.batch_read_label = QLabel("Batch Read:")
        self.batch_read_edit = QTextEdit()

        self.batch_write_button = QPushButton("WRITE")
        self.batch_write_button.clicked.connect(self.handle_batch_write_button)

        self.batch_read_button = QPushButton("READ")
        self.batch_read_button.clicked.connect(self.handle_batch_read_button)

        VL_3.addWidget(self.batch_write_label)
        VL_3.addWidget(self.batch_write_edit)

        VL_3.addWidget(self.batch_read_label)
        VL_3.addWidget(self.batch_read_edit)

        HL_7 = QHBoxLayout()
        HL_7.addWidget(self.batch_write_button)
        HL_7.addWidget(self.batch_read_button)

        VL_3.addLayout(HL_7)

        # Register Record
        self.register_record_label = QLabel("Register Record:")
        self.register_record_edit = QTextEdit()
        # self.register_record_edit.setMaximumHeight(self.height * 0.65)
        self.register_record_edit.setReadOnly(True)

        VL_3.addWidget(self.register_record_label)
        VL_3.addWidget(self.register_record_edit)

    def connect_signals(self):
        self.connect_button.clicked.connect(self.handle_connect_button)
        self.serial_reader.dataReceived.connect(self.handle_log)

    def update_figure(self, data):
        self.y[:-1] = self.y[1:]
        self.y[-1] = data
        self.line1.set_ydata(self.y[:, 0])
        self.line2.set_ydata(self.y[:, 1])
        self.canvas.draw()

    def update_serial_ports(self):
        self.serial_port_combo.clear()
        ports = serial.tools.list_ports.comports()  # 获取当前可用的串口列表

        # Create a dictionary to store port names and their corresponding devices
        port_dict = {}
        for port in ports:
            port_dict[port.device] = port


        if self.config.get("serial_port.selected_port") in port_dict.keys():
            selected_port_name = port_dict[self.config.data["serial_port"]
                                           ["selected_port"]].name
            selected_port_description = port_dict[self.config.data["serial_port"]
                                                  ["selected_port"]].description
            self.serial_port_combo.addItem(
                f"{selected_port_name} - {selected_port_description}")
        else:
            self.config.set("serial_port.selected_port", "")

        for port_device, port in port_dict.items():
            if port_device != self.config.data["serial_port"]["selected_port"]:
                self.serial_port_combo.addItem(
                    f"{port.name} - {port.description}")

    def serial_open(self, selected_port):
        sp = serial.Serial(port=selected_port, baudrate=self.baudrate, bytesize=self.bytesize,
                           parity=self.parity, stopbits=self.stopbits)

        if sp.is_open:
            pass
        else:
            sp.open()
            self.config.set("serial_port.selected_port", selected_port)

        self.serialport = sp

    def handle_connect_button(self):
        if not self.is_connected:

            selected_port_text = self.serial_port_combo.currentText()

            # in windows
            selected_port = selected_port_text.split(
            )[-1].replace("(", "").replace(")", "")  # 反向切分获取 port.device

            try:

                self.serial_open(selected_port)
                self.is_connected = True

                self.connect_button.setText("DISCONNECT")
                self.log_output.clear()

                log_name = self.config.get("log.log_prefix") + "_" +\
                    datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".log"
                self.log_buffer = self.log_path + log_name

                self.read_thread = QThread(self)
                self.serial_reader = SerialReader(self.serialport)
                self.serial_reader.dataReceived.connect(self.handle_log)
                self.serial_reader.moveToThread(self.read_thread)

                self.read_thread.finished.connect(
                    self.read_thread.deleteLater)
                self.read_thread.finished.connect(
                    self.serial_reader.deleteLater)

                self.read_thread.started.connect(self.serial_reader.start)
                self.read_thread.start()

            except Exception as e:
                self.is_connected = False
                error_message = f"Serial Port Open Failed: {str(e)}"
                QMessageBox.critical(self, "Error", error_message, QMessageBox.Ok)

        else:
            self.is_connected = False

            if self.serialport and self.serialport.is_open:
                self.serial_reader.stop()
                self.serialport.close()
                self.serialport = None

            if self.read_thread and self.read_thread.isRunning():
                self.read_thread.quit()
                self.read_thread.wait()

            self.connect_button.setText("CONNECT")

        self.update_serial_ports()

    def handle_plot_button(self):
        if not self.is_connected:
            QMessageBox.critical(self, "Error", "Serial Port is not connected", QMessageBox.Ok)
            return
        
        if not self.is_plotting:
            # 检查输入参数是否合法
            try:
                column = int(self.column_edit.text())
                row = int(self.row_edit.text())
                delimiter = self.delimiter_edit.text()
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Input Error: {str(e)}", QMessageBox.Ok)
                return

            if column <= 0 or row <= 0:
                QMessageBox.critical(self, "Error", "Input Error: Column or Row is less than 0", QMessageBox.Ok)
                return
            
            if delimiter == "":
                QMessageBox.critical(self, "Error", "Input Error: Delimiter is empty", QMessageBox.Ok)
                return

            self.column = column
            self.row = row
            self.delimiter = delimiter

            self.config.set("csv.column", self.column)
            self.config.set("csv.row", self.row)
            self.config.set("csv.delimiter", self.delimiter)

            self.is_plotting = True
            self.plot_button.setText("UNPLOT")

            # # # Create a thread to continuously update the plot
            # self.plot_thread = QThread(self)
            # self.plot_updater = PlotUpdater(self.log_output, self.plot_curve, self.x_axis_length, self.column)
            # self.plot_updater.moveToThread(self.plot_thread)

            # self.plot_thread.finished.connect(self.plot_thread.deleteLater)
            # self.plot_thread.finished.connect(self.plot_updater.deleteLater)

            # self.plot_thread.started.connect(self.plot_updater.start)
            # self.plot_thread.start()
            self.start_plot_updater()

        else:

            self.is_plotting = False
            self.plot_button.setText("PLOT")

            if hasattr(self, 'plot_thread') and self.plot_thread.isRunning():
                self.plot_updater.stop()
                self.plot_thread.quit()
                self.plot_thread.wait()

    def handle_write_button(self):
        pass

    def handle_read_button(self):
        pass

    def handle_batch_write_button(self):
        pass

    def handle_batch_read_button(self):
        pass

    def handle_log(self, data_str):
        cursor = self.log_output.textCursor()
        cursor.movePosition(QTextCursor.EndOfBlock)
        cursor.insertText(data_str)
        self.log_output.setTextCursor(cursor)
        self.log_output.ensureCursorVisible()

                # 如果文件不存在，则创建即可
        if not os.path.exists(self.log_buffer):
            with open(self.log_buffer, "w") as file:
                pass

        with open(self.log_buffer, "a") as file:
            file.write(data_str.split("\n")[0])
            # file.write("\n")

    def start_plot_updater(self):
        self.plot_thread = QThread(self)
        self.plot_updater = PlotUpdater(self.log_output, self.plot_curve, self.x_axis_length, self.column)
        self.plot_updater.moveToThread(self.plot_thread)

        self.plot_thread.finished.connect(self.plot_thread.deleteLater)
        self.plot_thread.finished.connect(self.plot_updater.deleteLater)

        self.plot_thread.started.connect(self.plot_updater.start)
        self.plot_thread.start()

        self.timer.start(self.timer_interval)
    
    def update_plot(self):
        if hasattr(self, 'plot_updater'):
            self.plot_updater.update_plot()

class SerialReader(QObject):
    dataReceived = Signal(str)

    def __init__(self, serial_port):
        super().__init__()
        self.sp = serial_port
        self.serial_port = serial_port
        self.line_buffer = b''
        self.is_running = False

    def run(self):
        while self.is_running and self.serial_port.is_open:
            if self.serial_port.in_waiting:
                self.line_buffer = self.serial_port.readline()
                self.line_buffer = self.line_buffer.decode('utf-8')
                self.dataReceived.emit(self.line_buffer)

    def start(self):
        self.is_running = True
        self.run()

    def stop(self):
        self.is_running = False
        if self.serial_port.is_open:
            self.serial_port.close()

class PlotUpdater(QObject):
    update_signal = Signal()

    def __init__(self, log_output, plot_curve, x_axis_length, column):
        super().__init__()
        self.x_axis_length = x_axis_length
        self.log_output = log_output
        self.plot_curve = plot_curve
        self.is_running = False
        self.column = column

        self.x_values = []
        self.y_values = []

    def start(self):
        self.is_running = True
        while self.is_running:
            new_data = self.log_output.toPlainText().splitlines()[-1]

            if new_data:
                csv_values = new_data.split('\t')
                if len(csv_values) > self.column:
                    value = float(csv_values[self.column])
                    self.x_values.append(len(self.x_values))
                    self.y_values.append(value)

                    if len(self.x_values) > self.x_axis_length:
                        self.x_values.pop(0)
                        self.y_values.pop(0)

            time.sleep(0.1)

    def stop(self):
        self.is_running = False

    def update_plot(self):
        # 清空绘图区域
        self.plot_curve.scene().clear()

        x = np.array(self.x_values)
        y = np.array(self.y_values)

        if len(x) > 1:
            path = QPainterPath(QPointF(x[0], y[0]))

            for i in range(1, len(x)):
                path.lineTo(x[i], y[i])

            pen = QPen(QColor(0, 0, 255))  # 蓝色画笔
            brush = QBrush(QColor(0, 0, 255, 50))  # 蓝色填充

            # 创建 QGraphicsPathItem 并添加到场景中
            path_item = QGraphicsPathItem(path)
            path_item.setPen(pen)
            path_item.setBrush(brush)
            self.plot_curve.scene().addItem(path_item)

        self.plot_curve.scene().setSceneRect(self.plot_curve.scene().itemsBoundingRect())



if __name__ == '__main__':
    app = QApplication(sys.argv)
    pg.setConfigOptions(antialias=True)
    window = RealTimePlotApp()
    window.show()
    sys.exit(app.exec())
